﻿#region Namespace References

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using DynConDevCorp.OpenArch.SmartData.Core.EntityTypeLevel;
using DynConDevCorp.OpenArch.SmartData.Core.Metadata;
using DynConDevCorp.OpenArch.SmartData.Interfaces;
using DynConDevCorp.OpenArch.SmartData.Interfaces.Attributes;
using DynConDevCorp.OpenArch.SmartData.Interfaces.EventArgClasses;
using DynConDevCorp.OpenArch.SmartData.Interfaces.MetaData;

#endregion

namespace DynConDevCorp.OpenArch.SmartData.Core
{
    /// <summary>
    /// 
    /// </summary>
    public class SmartEntity : ISmartEntity
    {
        public override string ToString()
        {
            return String.Format("{0} ID:{1}", GetType().Name, EntityID);
        }
        public virtual void Dispatch(ISmartDispatcher dispatcher, object context)
        {
            dispatcher.ProcessDispatch(this, context);
        }

        /// <summary>
        /// Gets a value indicating whether this instance is dynamic entity.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is dynamic entity; otherwise, <c>false</c>.
        /// </value>
        public virtual Boolean IsDynamicEntity { get { return false; } }

        public virtual ISmartMetaEntity MetaEntity
        {
            get { return SmartMetaDataStore.GlobalInstance.GetMetaEntity(this.GetType());  }
        }

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SmartEntity"/> class.
        /// </summary>
        public SmartEntity()
        {
            RegisterAutoFields();
            RegisterTypeLevelData();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SmartEntity"/> class.
        /// </summary>
        public SmartEntity(Guid EntityID)
        {
            m_EntityID = EntityID;
            RegisterAutoFields();
            RegisterTypeLevelData();
        }

        #endregion

        #region ISmartEntity Members

        /// <summary>
        /// Adds the field effect.
        /// </summary>
        /// <typeparam name="TData">The type of the data.</typeparam>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="effect">The effect.</param>
        public void AddFieldEffect<TData>(string fieldName, ISmartEffect<TData> effect)
        {
            ISmartEffectCollection fieldEffectCollection;
            if (!m_FieldEffects.TryGetValue(fieldName, out fieldEffectCollection))
            {
                fieldEffectCollection = new SmartEffectCollection();
                m_FieldEffects.Add(fieldName, fieldEffectCollection);
            }
            fieldEffectCollection.Add(effect);
        }

        public void AddFieldEffect<TData>(string fieldName, EventHandler<ValueChangedEventArgs<TData>> eventHandler)
        {
            SmartEffect<TData> newEffect = new SmartEffect<TData>(eventHandler);
            AddFieldEffect(fieldName, newEffect);
        }

        /// <summary>
        /// Commits this instance.
        /// </summary>
        public virtual void Commit()
        {
            foreach (ISmartField field in m_Fields.Values)
                field.Commit();
        }

        /// <summary>
        /// Determines whether the specified field name contains field.
        /// </summary>
        /// <param name="fieldName">Name of the field.</param>
        /// <returns>
        /// 	<c>true</c> if the specified field name contains field; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsField(string fieldName)
        {
            return m_Fields.ContainsKey(fieldName);
        }

        /// <summary>
        /// Occurs when [display value changed].
        /// </summary>
        public virtual event EventHandler<DisplayValueChangedEventArgs> DisplayValueChanged;

        /// <summary>
        /// Enables the track changes.
        /// </summary>
        /// <param name="state">if set to <c>true</c> [state].</param>
        public virtual void EnableTrackChanges(bool state)
        {
            foreach (ISmartField field in m_Fields.Values)
                field.EnableTrackChanges(state);
        }

        /// <summary>
        /// Occurs when [field value changed].
        /// </summary>
        public event EventHandler<FieldValueChangedEventArgs> FieldValueChanged
        {
            add { m_FieldValueChanged += value; }
            remove { m_FieldValueChanged -= value; }
        }

        /// <summary>
        /// Formatteds the display.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <returns></returns>
        public virtual string FormattedDisplay(string format)
        {
            throw new NotSupportedException();
        }

        public string GetFieldName(ISmartField field)
        {
            foreach (var registeredField in m_Fields)
            {
                if (ReferenceEquals(field, registeredField.Value))
                    return registeredField.Key;
            }
            return String.Empty;
        }

        /// <summary>
        /// Marks as dirty.
        /// </summary>
        public virtual void MarkAsDirty()
        {
            foreach (ISmartField field in m_Fields.Values)
                field.MarkAsDirty();
        }

        public void PropogateFieldEffects<TData>(ISmartField source, ValueChangedEventArgs<TData> args)
        {
            string fieldName = GetFieldName(source);
            ValueChangedEventArgs<TData> newArgs = new ValueChangedEventArgs<TData>(args, fieldName);
            SmartEffectCollection effects = m_TypeLevelData.GetFieldEffects();
            foreach (SmartEffect baseEffect in effects)
            {
                SmartEffect<TData> typedEffect = baseEffect as SmartEffect<TData>;
                if (typedEffect != null)
                {
                    typedEffect.InvokeEffect(this, newArgs);
                }
                else
                {
                    baseEffect.Execute(newArgs.EffectCause);
                }
            }
            ISmartEffectCollection fieldEffectCollection;

            if (m_FieldEffects.TryGetValue(args.FieldName, out fieldEffectCollection))
            {
                foreach (ISmartEffect rawFieldEffect in fieldEffectCollection)
                {
                    ISmartEffect<TData> typedFieldEffect = rawFieldEffect as ISmartEffect<TData>;
                    if (typedFieldEffect != null)
                    {
                        typedFieldEffect.InvokeEffect(this, newArgs);
                    }
                }
            }

            if (OwningEntity != null)
            {
                OwningEntity.PropogateFieldEffects(this, newArgs);
            }
            foreach (ISmartEntity linkedEntitity in m_LinkedEntities)
            {
                linkedEntitity.PropogateFieldEffects(this, newArgs);
            }
        }

        /// <summary>
        /// Occurs when [raw value changed].
        /// </summary>
        public virtual event EventHandler<ValueChangedEventArgs<object>> RawValueChanged;

        /// <summary>
        /// Registers the auto fields.
        /// </summary>
        public void RegisterAutoFields()
        {
            SmartMetadataManager.Global.RegisterAutoFields(this);
        }

        /// <summary>
        /// Registers the field.
        /// </summary>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="field">The field.</param>
        /// <returns></returns>
        public virtual ISmartField RegisterField(string fieldName, ISmartField field)
        {
            m_Fields.Add(fieldName, field);
            field.SetOwningEntity(this);
            AddFieldValueChangedEffect(field);
            return field;
        }

        public void RegisterFieldEffectForType<TData>(string fieldName, ISmartEffect<TData> effect)
        {
            SmartEntityTypeLevelLibrary.Global.RegisterFieldEffectForType(GetType(), fieldName, effect);
        }

        public void RegisterFieldEffectForType<TData>(string fieldName,
                                                      EventHandler<ValueChangedEventArgs<TData>> eventHandler)
        {
            SmartEffect<TData> newEffect = new SmartEffect<TData>(eventHandler);
            RegisterFieldEffectForType(fieldName, newEffect);
        }

        /// <summary>
        /// Removes the field effect.
        /// </summary>
        /// <typeparam name="TData">The type of the data.</typeparam>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="effect">The effect.</param>
        public void RemoveFieldEffect<TData>(string fieldName, ISmartEffect<TData> effect)
        {
            ISmartEffectCollection fieldEffectCollection;
            if (m_FieldEffects.TryGetValue(fieldName, out fieldEffectCollection))
            {
                fieldEffectCollection.Remove(effect);
                if (fieldEffectCollection.Count == 0)
                    m_FieldEffects.Remove(fieldName);
            }
        }

        /// <summary>
        /// Rollback value to last Committed Value.
        /// </summary>
        public virtual void Rollback()
        {
            foreach (ISmartField field in m_Fields.Values)
                field.Rollback();
        }

        /// <summary>
        /// Sets the entity which has direct ownership of this instance.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public virtual void SetOwningEntity(ISmartEntity entity)
        {
            m_OwningEntity = entity;
        }

        /// <summary>
        /// Sets the raw field value.
        /// </summary>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="value">The value.</param>
        public virtual void SetRawFieldValue(string fieldName, object value)
        {
            m_Fields[fieldName].RawValue = value;
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        /// Gets the type of the data.
        /// </summary>
        /// <value>The type of the data.</value>
        public virtual Type DataType
        {
            get { throw new NotSupportedException(); }
        }

        public string DisplayFormatString { get; set; }

        /// <summary>
        /// Gets the display value.
        /// </summary>
        /// <value>The display value.</value>
        public virtual string DisplayValue
        {
            get { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Gets the entity ID.
        /// </summary>
        /// <value>The entity ID.</value>
        public virtual Guid EntityID
        {
            get { return m_EntityID; }
        }

        /// <summary>
        /// Gets the field count.
        /// </summary>
        /// <value>The field count.</value>
        public virtual int FieldCount
        {
            get { return m_Fields.Count; }
        }

        /// <summary>
        /// Gets the field names.
        /// </summary>
        /// <value>The field names.</value>
        public virtual IList<string> FieldNames
        {
            get { return new List<string>(m_Fields.Keys); }
        }

        /// <summary>
        /// Gets the fields.
        /// </summary>
        /// <value>The fields.</value>
        [Persist(IsXmlPersistable = false, IsDatabaseField = false)]
        public virtual ISmartFieldKeyedCollection<string, ISmartField> Fields
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [input inhibited].
        /// </summary>
        /// <value><c>true</c> if [input inhibited]; otherwise, <c>false</c>.</value>
        public virtual PartialBoolean InputInhibited
        {
            get
            {
                bool allTrue = true;
                bool allFalse = true;
                foreach (ISmartField field in m_Fields.Values)
                {
                    if ((bool) field.InputInhibited)
                        allFalse = false;
                    else
                        allTrue = false;
                }
                if (allTrue)
                    return true;
                if (allFalse)
                    return false;
                return PartialBoolean.Mixed;
            }
            set
            {
                foreach (ISmartField field in m_Fields.Values)
                {
                    field.InputInhibited = value;
                }
            }
        }

        /// <summary>
        /// Sets the input value.
        /// </summary>
        /// <value>The input value.</value>
        public virtual string InputValue
        {
            set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is dirty.
        /// </summary>
        /// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
        public virtual PartialBoolean IsDirty
        {
            get
            {
                foreach (ISmartField field in m_Fields.Values)
                {
                    if ((bool) field.IsDirty)
                        return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Gets the <see cref="DynConDevCorp.OpenArch.SmartData.Interfaces.ISmartField"/> with the specified field name.
        /// </summary>
        /// <value></value>
        [Persist(IsXmlPersistable = false, IsDatabaseField = false)]
        public virtual ISmartField this[string fieldName]
        {
            get { return m_Fields[fieldName]; }
        }

        /// <summary>
        /// Gets the entity which has direct ownership of this instance.
        /// </summary>
        /// <value>The owning entity.</value>
        public virtual ICollection<ISmartEntity> LinkedEntities
        {
            get { return m_LinkedEntities; }
        }

        /// <summary>
        /// Gets the entity which has direct ownership of this instance.
        /// </summary>
        /// <value>The owning entity.</value>
        [Persist(IsXmlPersistable = false, IsDatabaseField = false)]
        public virtual ISmartEntity OwningEntity
        {
            get { return m_OwningEntity; }
        }

        /// <summary>
        /// Gets or sets the state of the persistance.
        /// </summary>
        /// <value>The state of the persistance.</value>
        public virtual SmartPersistenceState PersistenceState
        {
            get { return m_PersistenceState; }
            set { m_PersistenceState = value; }
        }

        /// <summary>
        /// Gets or sets the raw value.
        /// </summary>
        /// <value>The raw value.</value>
        public virtual object RawValue
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Gets a value indicating whether [track changes].
        /// </summary>
        /// <value><c>true</c> if [track changes]; otherwise, <c>false</c>.</value>
        public virtual PartialBoolean TrackingChanges
        {
            get
            {
                bool allTrue = true;
                bool allFalse = true;
                foreach (ISmartField field in m_Fields.Values)
                {
                    if ((bool) field.TrackingChanges)
                        allFalse = false;
                    else
                        allTrue = false;
                }
                if (allTrue)
                    return true;
                if (allFalse)
                    return false;
                return PartialBoolean.Mixed;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [read only].
        /// </summary>
        /// <value><c>true</c> if [read only]; otherwise, <c>false</c>.</value>
        public virtual PartialBoolean WriteLocked
        {
            get
            {
                bool allTrue = true;
                bool allFalse = true;
                foreach (ISmartField field in m_Fields.Values)
                {
                    if ((bool) field.WriteLocked)
                        allFalse = false;
                    else
                        allTrue = false;
                }
                if (allTrue)
                    return true;
                if (allFalse)
                    return false;
                return PartialBoolean.Mixed;
            }
            set
            {
                foreach (ISmartField field in m_Fields.Values)
                {
                    field.WriteLocked = value;
                }
            }
        }

        #endregion

        #region Public Instance Methods

        public void RegisterFieldEffectForType(SmartEffect effect)
        {
            m_TypeLevelData.RegisterFieldEffectForType(effect);
        }

        public void RegisterTypeLevelData()
        {
            m_TypeLevelData = SmartEntityTypeLevelLibrary.Global.RegisterIfNecessary(GetType());
        }

        #endregion

        #region Private Instance Methods

        private void AddFieldValueChangedEffect(ISmartField field)
        {
            field.RawValueChanged += Field_RawValueChanged;
        }

        private void Field_RawValueChanged(object sender, ValueChangedEventArgs<object> args)
        {
            string fieldName = String.Empty;
            foreach (var pair in m_Fields)
            {
                if (ReferenceEquals(pair.Value, args.Originator))
                    fieldName = pair.Key;
            }
            FieldValueChangedEventArgs newEventArgs = new FieldValueChangedEventArgs(this, fieldName, args.OldValue,
                                                                                     args.NewValue);
            Fire_FieldValueChanged(this, newEventArgs);
        }

        private void Fire_FieldValueChanged(object sender, FieldValueChangedEventArgs eventArgs)
        {
            if (m_FieldValueChanged != null)
                m_FieldValueChanged(sender, eventArgs);
        }

        #endregion

        #region Fields and Constants

        private readonly Guid m_EntityID = Guid.NewGuid();

        private readonly Dictionary<String, ISmartEffectCollection> m_FieldEffects =
            new Dictionary<String, ISmartEffectCollection>();

        private readonly Dictionary<string, ISmartField> m_Fields = new Dictionary<string, ISmartField>();
        private readonly Collection<ISmartEntity> m_LinkedEntities = new Collection<ISmartEntity>();

        private EventHandler<FieldValueChangedEventArgs> m_FieldValueChanged;
        private ISmartEntity m_OwningEntity;
        private SmartPersistenceState m_PersistenceState;
        private SmartEntityTypeLevelData m_TypeLevelData;

        #endregion
    }
}